+/*
+/*
+ * RGB(W) LED IM Dendrite module (for ESP8266)
+ * Created for Interplaymedium™ project (https://interplaymedium.org)
+ * Copyright © 2016 Dmitry Shalnov [interplaymedium.org]
+ * Licensed under the Apache License, Version 2.0
+*/
+
+#include "../../info"
+
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+#include <EEPROM.h>
+
+#define VERSION "0.1.4"
+
+#define EEPROM_STR_MAX_LEN 16
+#define EEPROM_OTHER_MAX_LEN 8
+
+#define EEPROM_STR 0
+#define EEPROM_STR_FLAG 17 // watch overlap: EEPROM_STR_MAX_LEN
+#define EEPROM_R 18
+#define EEPROM_G 19
+#define EEPROM_B 20
+#define EEPROM_W 21
+#define EEPROM_ROT_H 22
+#define EEPROM_ROT_L 23
+
+// -------------------- PWM settings ----------------------------------------------------
+
+extern "C"{
+ #include "pwm.h" // Includes of Expressif SDK
+}
+
+#define LED1 0
+#define LED2 2
+#define LED3 3
+#define LED4 1
+
+#define PWM_CHANNELS 4
+
+#define PWMSTEPS 255
+#define PWM_PERIOD 5000 // Period of PWM frequency, SDK default: 5000 -> * 200ns ^= 1 kHz
+
+unsigned int CIEL8[ PWMSTEPS ];
+
+int R=0, G=0, B=0, W=0;
+unsigned int Rnew=0, Gnew=0, Bnew=0, Wnew=0;
+unsigned int rotateDelay = 0;
+int signR = 1, signG = 1, signB = 1, signW = 1;
+unsigned int eTimer = 0;
+
+uint32 pwm_duty_init[PWM_CHANNELS]; // PWM initial duty: OFF by default
+
+uint32 io_info[PWM_CHANNELS][3] = {
+
+// MUX, FUNC, PIN
+
+// {PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5, 5}, // D1
+// {PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4, 4}, // D2
+ {PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0, LED1}, // D3 0
+ {PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2, LED2}, // D4 2
+
+ {PERIPHS_IO_MUX_U0RXD_U, FUNC_GPIO3, LED3}, // RX 3
+ {PERIPHS_IO_MUX_U0TXD_U, FUNC_GPIO1, LED4}, // TX 1
+
+// {PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14, 14}, // D5
+// {PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12, 12}, // D6
+// {PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13, 13}, // D7
+// {PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15 ,15}, // D8
+
+// D0 - not have PWM :-( 16
+};
+
+// ------------------- server settings --------------------------------------------------
+
+#define HOST_DEFAULT "im_rgb5"
+
+char host[ EEPROM_STR_MAX_LEN ];
+
+const char* ssid = IM_WIFI_SSID;
+const char* password = IM_WIFI_PASS;
+
+ESP8266WebServer server(80);
+
+String HTTPresp;
+
+// ----------------- change current R G B (W) to another RGBW set ----------------------
+
+void changeRGBto( int newR, int newG, int newB, int newW ){
+
+ float stepR, stepG, stepB, stepW;
+ int R2, G2, B2, W2;
+/*
+// int R, G, B, W;
+
+// R = EEPROM.read( 0 );
+// G = EEPROM.read( 1 );
+// B = EEPROM.read( 2 );
+// W = EEPROM.read( 3 );
+*/
+ stepR = (newR - R) / (float)PWMSTEPS;
+ stepG = (newG - G) / (float)PWMSTEPS;
+ stepB = (newB - B) / (float)PWMSTEPS;
+ stepW = (newW - W) / (float)PWMSTEPS;
+
+ for ( unsigned int a = 0; a < PWMSTEPS; a++ ){
+
+ R2 = R + round( stepR * (float)a );
+ G2 = G + round( stepG * (float)a );
+ B2 = B + round( stepB * (float)a );
+ W2 = W + round( stepW * (float)a );
+
+ pwm_set_duty( CIEL8[R2], 0 );
+ pwm_set_duty( CIEL8[G2], 1 );
+ pwm_set_duty( CIEL8[B2], 2 );
+ pwm_set_duty( CIEL8[W2], 3 );
+
+ pwm_start(); // commit
+
+ delay( 500 / PWMSTEPS );
+ }
+}
+
+// ----------- explode for selected substring -----------
+
+String expld( String str, unsigned int numb, char delimiter ){
+
+ unsigned int cnt = 0, a = 0, p2 = 0, p1 = 0, lng = 0;
+
+ lng = str.length();
+
+ for ( a = 0; a < lng; a++ ){
+ if ( str.charAt( a ) == delimiter ) {
+ p2 = p1;
+ p1 = a;
+ if ( cnt == numb ) break;
+ cnt ++;
+ }
+ }
+
+ if ( a == lng ) {
+ p2 = p1;
+ p1 = lng;
+ }
+
+ if ( numb > 0 ) p2 ++;
+
+ return str.substring(p2, p1);
+
+}
+
+unsigned char URIHasArg( String str, String arg ){
+
+
+ unsigned char a = 0, delimiterCnt = 0, lng = str.length();
+
+ for ( a = 0; a < lng; a++ ){
+ if ( str.charAt( a ) == '/' ) delimiterCnt++;
+ }
+
+ for ( a=0; a < 10; a++ ){
+ if ( arg.equals( expld( str, a, '/' ) ) ) return a;
+ }
+ return 0;
+}
+
+// ---------------- EEPROM String r/w -------------------
+
+void EEPROMStrRead( unsigned char addr, char * str ){
+ for (unsigned char a = addr; a < EEPROM_STR_MAX_LEN; a++ ){
+ str[ a ] = EEPROM.read( a );
+ if (str[ a ] == 0) break;
+ }
+}
+
+void EEPROMStrWrite( unsigned char addr, char * str ){
+ unsigned char a = 0;
+ for (a = addr; a < EEPROM_STR_MAX_LEN; a++ ){
+ EEPROM.write( a, str[ a ] );
+ if (str[ a ] == 0) break;
+ }
+ EEPROM.write( a, 0 );
+}
+
+// ---------------- misc ---------------------------------
+
+int str2HEX(const String str) {
+ return strtol( str.c_str(), 0, 16 );
+}
+
+void blink( unsigned char times ){
+ for (unsigned char a = 0; a < times; a++ ){
+ digitalWrite(LED4, HIGH);
+ delay (50);
+ digitalWrite(LED4, LOW);
+ delay (50);
+ }
+}
+
+// --------------- Init --------------------------------
+
+void setup(void) {
+
+ HTTPresp.reserve(800); // lenght of Help message generally
+
+ // calculate lookup array
+
+ for (unsigned int a = 1; a < PWMSTEPS; a++) CIEL8[ a ] = round( pow( PWM_PERIOD, (double)a / (PWMSTEPS-1) ) ); // calculate exponential lookup array for PWM
+ CIEL8[ 0 ] = 0;
+
+ // init LED pins
+
+ pinMode(LED1, OUTPUT);
+ pinMode(LED2, OUTPUT);
+ pinMode(LED3, OUTPUT);
+ pinMode(LED4, OUTPUT);
+
+ digitalWrite(LED1, LOW);
+ digitalWrite(LED2, LOW);
+ digitalWrite(LED3, LOW);
+ digitalWrite(LED4, LOW);
+
+ // PWM inti
+
+ for (uint8_t channel = 0; channel < PWM_CHANNELS; channel++) pwm_duty_init[channel] = 0; // Initial duty -> all off
+ uint32_t period = PWM_PERIOD;
+ pwm_init(period, pwm_duty_init, PWM_CHANNELS, io_info);
+ pwm_start();
+
+ // Serial init
+#if SERIAL == 1
+ Serial.begin(115200);
+ Serial.println();
+ Serial.println("Booting Sketch...0");
+#endif
+
+ // mDNS init
+
+// deviceURI.reserve(7);
+// deviceURI = "im" + WiFi.macAddress().substring(12, 14) + WiFi.macAddress().substring(15, 17); // 00:00:00:00:00:00
+
+ EEPROM.begin( EEPROM_STR_MAX_LEN + EEPROM_OTHER_MAX_LEN );
+
+ if ( EEPROM.read( EEPROM_STR_FLAG ) == 1 ){
+ EEPROMStrRead( EEPROM_STR, host );
+ } else {
+ strcpy( host, HOST_DEFAULT ); // default URI and host name
+ }
+
+ WiFi.hostname( host );
+
+// WiFi.softAP(APssid, APpassword);
+
+// WiFi.mode(WIFI_AP);
+// WiFi.mode(WIFI_AP_STA);
+ WiFi.mode(WIFI_STA);
+ WiFi.begin(ssid, password);
+
+ if (WiFi.waitForConnectResult() == WL_CONNECTED) {
+
+// MDNS.begin( deviceURI.c_str() );
+ MDNS.begin( host );
+
+ // default
+
+ server.onNotFound( []() {
+ server.sendHeader("Connection", "close");
+
+ unsigned int RGBW = 0xff;
+ String param = "";
+ String command = "";
+
+ // parse plain URI arguments TODO: not sure whether we need it, check Plan 9 specifications
+/*
+ param = server.arg("rgbw");
+ if ( param == "" ) {
+ param = URIHasArg( server.uri(), "rgbw" );
+ }
+*/
+ // color change
+
+ if ( server.hasArg("rgbw") ){ // || param != 0
+
+ param = server.arg("rgbw");
+
+ Rnew = str2HEX( param.substring(0, 2) );
+ Gnew = str2HEX( param.substring(2, 4) );
+ Bnew = str2HEX( param.substring(4, 6) );
+ Wnew = str2HEX( param.substring(6, 8) );
+
+ changeRGBto( Rnew, Gnew, Bnew, Wnew );
+
+ R = Rnew;
+ G = Gnew;
+ B = Bnew;
+ W = Wnew;
+ }
+
+ // rotate
+
+ if ( server.hasArg("rotate") ){
+ rotateDelay = str2HEX( server.arg("rotate") );
+ }
+
+ // rename host
+
+ if ( server.hasArg("rename") ) {
+ EEPROMStrWrite( EEPROM_STR, (char *)server.arg("rename").c_str() );
+ EEPROM.write( EEPROM_STR_FLAG, 1 );
+ EEPROM.commit();
+
+ server.sendHeader("Connection", "close");
+ server.send_P(200, "text/plain", PSTR("Done. System rebooting...\n") );
+
+ delay(500);
+ ESP.restart();
+ }
+
+ // ------
+
+ HTTPresp = "host: " + String(host) + ".lan" + "\n";
+ HTTPresp += "URI: " + server.uri() + "\n";
+// HTTPresp += "Arg RGBW: " + String( URIHasArg( server.uri(), "test2" ) ) + "\n"; // URI contains /test2/
+// HTTPresp += "Arg RGBW: " + server.arg("aaaa") + "\n"; // GET or POST params has "aaaa"
+ HTTPresp += "rotate: " + String(rotateDelay) + "\n";
+ HTTPresp += "rgbw: " + String(R) + " " + String(G) + " " + String(B) + " " + String(W) + "\n";
+
+ server.send(200, "text/plain", HTTPresp);
+ });
+
+ // test EEPROM string
+
+ server.on("/eeprom", HTTP_GET, []() {
+
+ char * testStr2 = "asdfghjkl12345";
+
+ server.setContentLength(CONTENT_LENGTH_UNKNOWN);
+ server.send(200, "text/plain", "");
+
+ EEPROMStrRead( EEPROM_STR, testStr2 );
+
+ server.sendContent( testStr2 );
+ server.sendContent( "\n" );
+ });
+
+ // help
+
+ server.on("/help", HTTP_GET, []() {
+
+ HTTPresp = "Interplay Medium ESP8266 LED RGB(W) PWM Controller. Version: " + String(VERSION) + "\n";
+ HTTPresp += "Written by Dmitry Shalnov (c) 2017. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. \n\n";
+
+ HTTPresp += " rgbw Red Green Blue and White components in hexadecimal form (e.g. ff00ff00)\n";
+ HTTPresp += " rotate Delay of components rotation FX in hexadecimal (e.g. ffff), set 0 to stop\n";
+ HTTPresp += " update Wireless update of firmware (see example below)\n";
+ HTTPresp += " help Send this help\n\n";
+
+ HTTPresp += "Usage: curl http://" + String(host) + ".lan [--data \"rgbw=<hex RGBW>\"] [--data \"rotate=<hex delay, 0 = stop >\"] \n";
+ HTTPresp += " curl http://" + String(host) + ".lan[/rgbw/<hex RGBW>][/rotate/<hex delay>] \n";
+ HTTPresp += "Examples: curl http://" + String(host) + ".lan/?rgbw=00ff00ff&rotate=ff \n";
+ HTTPresp += " curl -F image=@RGBW_Controller.bin " + String(host) + ".lan/update \n";
+
+ server.sendHeader("Connection", "close");
+ server.send( 200, "text/plain", HTTPresp );
+
+ });
+
+ // OTA update
+
+ server.on("/update", HTTP_POST, []() {
+
+ server.sendHeader("Connection", "close");
+ server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
+
+ ESP.restart();
+
+ }, []() {
+
+ HTTPUpload& upload = server.upload();
+
+ if (upload.status == UPLOAD_FILE_START) {
+
+ rotateDelay = 0;
+ blink( 3 );
+
+#if SERIAL == 1
+ Serial.setDebugOutput(true);
+#endif
+ WiFiUDP::stopAll();
+
+#if SERIAL == 1
+ Serial.printf("Update: %s\n", upload.filename.c_str());
+#endif
+ uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
+
+ if (!Update.begin(maxSketchSpace)) { //start with max available size
+#if SERIAL == 1
+ Update.printError(Serial);
+#endif
+ }
+ } else if (upload.status == UPLOAD_FILE_WRITE) {
+ if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
+#if SERIAL == 1
+ Update.printError(Serial);
+#endif
+ }
+ } else if (upload.status == UPLOAD_FILE_END) {
+ if (Update.end(true)) { //true to set the size to the current progress
+
+ server.sendHeader("Connection", "close");
+ server.send_P(200, "text/plain", PSTR("Success. Please wait until device replace firmware and boot up...\n") );
+#if SERIAL == 1
+ Serial.printf("Update Success: %u\nRebooting....\n", upload.totalSize);
+#endif
+ } else {
+ server.sendHeader("Connection", "close");
+ server.send_P(200, "text/plain", PSTR("Something went wrong. Please reset device and try again.\n") );
+#if SERIAL == 1
+ Update.printError(Serial);
+#endif
+ }
+#if SERIAL == 1
+ Serial.setDebugOutput(false);
+#endif
+ }
+
+ yield();
+ });
+
+ // start server
+
+ server.begin();
+ MDNS.addService("http", "tcp", 80);
+#if SERIAL == 1
+ Serial.printf("Ready! Open http://%s.local in your browser\n", host);
+#endif
+
+ } else {
+#if SERIAL == 1
+ Serial.println("WiFi Failed");
+#endif
+ }
+}
+
+void loop(void) {
+
+ server.handleClient();
+ MDNS.update();
+
+ eTimer ++;
+
+ if ( eTimer >= rotateDelay && rotateDelay != 0 ) {
+
+ eTimer = 0;
+
+ R = R + signR;
+ G = G + signG;
+ B = B + signB;
+ W = W + signW;
+
+ if ( R >= PWMSTEPS-1 ) { signR = -1; R = PWMSTEPS-1; }
+ if ( G >= PWMSTEPS-1 ) { signG = -1; G = PWMSTEPS-1; }
+ if ( B >= PWMSTEPS-1 ) { signB = -1; B = PWMSTEPS-1; }
+ if ( W >= PWMSTEPS-1 ) { signW = -1; W = PWMSTEPS-1; }
+
+ if ( R < 2 ) { signR = 1; R = 2; }
+ if ( G < 2 ) { signG = 1; G = 2; }
+ if ( B < 2 ) { signB = 1; B = 2; }
+ if ( W < 2 ) { signW = 1; W = 2; }
+
+ pwm_set_duty( CIEL8[R], 0 );
+ pwm_set_duty( CIEL8[G], 1 );
+ pwm_set_duty( CIEL8[B], 2 );
+ pwm_set_duty( CIEL8[W], 3 );
+
+ pwm_start(); // commit
+
+ }
+}